package com.starleague.battletech.unit.mech;

import com.starleague.battletech.BattletechException;
import com.starleague.battletech.game.IBattletechGame;
import com.starleague.battletech.game.IBattletechTeam;
import com.starleague.battletech.unit.BattletechUnitEngineType;
import com.starleague.battletech.unit.BattletechUnitInternalStructureType;
import com.starleague.battletech.unit.IPilot;
import com.starleague.battletech.unit.IWeight;

public abstract class AbstractMechFactory<BIPED_MECH extends IMutableMech & IBipedMech, QUADRUPED_MECH extends IMutableMech & IQuadrupedMech>
        implements IMechFactory
{
    private final AbstractMechInternalStructureFactory internalStructureFactory;
    private final AbstractMechEngineFactory engineFactory;
    private final AbstractMechGyroFactory gyroFactory;

    protected AbstractMechFactory(AbstractMechInternalStructureFactory internalStructureFactory,
                                  AbstractMechEngineFactory engineFactory,
                                  AbstractMechGyroFactory gyroFactory)
    {
        this.internalStructureFactory = internalStructureFactory;
        this.engineFactory = engineFactory;
        this.gyroFactory = gyroFactory;
    }

    @Override
    public final BIPED_MECH createBipedMech(IBattletechGame game,
                                            IBattletechTeam team,
                                            IPilot pilot,
                                            IWeight weight,
                                            int walkMovementPoints,
                                            BattletechUnitInternalStructureType internalStructureType,
                                            BattletechUnitEngineType engineType,
                                            WhichArmActuators leftArmActuators,
                                            WhichArmActuators rightArmActuators)
            throws BattletechException
    {
        BIPED_MECH mech = createBipedMech(game, team, pilot, weight);

        addLeg(mech, MechHitLocationPosition.LEFT_LEG);
        addLeg(mech, MechHitLocationPosition.RIGHT_LEG);
        addArm(mech, MechHitLocationPosition.LEFT_ARM, leftArmActuators);
        addArm(mech, MechHitLocationPosition.RIGHT_ARM, rightArmActuators);

        populateMech(mech, walkMovementPoints, internalStructureType, engineType);
        return mech;
    }

    @Override
    public final QUADRUPED_MECH createQuadrupedMech(IBattletechGame game,
                                                    IBattletechTeam team,
                                                    IPilot pilot,
                                                    IWeight weight,
                                                    int walkMovementPoints,
                                                    BattletechUnitInternalStructureType internalStructureType,
                                                    BattletechUnitEngineType engineType)
            throws BattletechException
    {
        QUADRUPED_MECH mech = createQuadrupedMech(game, team, pilot, weight);

        addLeg(mech, MechHitLocationPosition.LEFT_LEG_FRONT);
        addLeg(mech, MechHitLocationPosition.RIGHT_LEG_FRONT);
        addLeg(mech, MechHitLocationPosition.LEFT_LEG_BACK);
        addLeg(mech, MechHitLocationPosition.RIGHT_LEG_BACK);

        populateMech(mech, walkMovementPoints, internalStructureType, engineType);
        return mech;
    }

    protected abstract BIPED_MECH createBipedMech(IBattletechGame game,
                                                  IBattletechTeam team,
                                                  IPilot pilot,
                                                  IWeight weight);

    protected abstract QUADRUPED_MECH createQuadrupedMech(IBattletechGame game,
                                                          IBattletechTeam team,
                                                          IPilot pilot,
                                                          IWeight weight);

    protected abstract IMechHitLocationLeg addLeg(IMutableMech mech,
                                                  MechHitLocationPosition position,
                                                  int internalStructurePoints)
            throws BattletechException;

    protected abstract IMechHitLocationArm addArm(BIPED_MECH mech,
                                                  MechHitLocationPosition position,
                                                  int internalStructurePoints)
            throws BattletechException;

    protected abstract IMechHitLocationHead addHead(IMutableMech mech,
                                                    int internalStructurePoints)
            throws BattletechException;

    protected abstract IMechHitLocationCenterTorso addCenterTorso(IMutableMech mech,
                                                                  int internalStructurePoints)
            throws BattletechException;

    protected abstract IMechHitLocationSideTorso addSideTorso(IMutableMech mech,
                                                              MechHitLocationPosition position,
                                                              int internalStructurePoints)
            throws BattletechException;

    protected abstract IMechCockpit addCockpit(IMechHitLocationHead head,
                                               int... slotIndices)
            throws BattletechException;

    protected abstract IMechCockpit addSensors(IMechHitLocationHead head,
                                               int... slotIndices)
            throws BattletechException;

    protected abstract IMechCockpit addLifeSupport(IMechHitLocationHead head,
                                                   int... slotIndices)
            throws BattletechException;

    protected abstract IMechActuator addActuator(IMechHitLocation hitLocation,
                                                 MechActuatorType actuatorType,
                                                 int criticalSlotIndex)
            throws BattletechException;

    private void populateMech(IMutableMech mech,
                              int walkMovementPoints,
                              BattletechUnitInternalStructureType internalStructureType,
                              BattletechUnitEngineType engineType)
            throws BattletechException
    {
        addHead(mech);
        addCenterTorso(mech);
        addSideTorso(mech, MechHitLocationPosition.LEFT_TORSO);
        addSideTorso(mech, MechHitLocationPosition.RIGHT_TORSO);

        // TODO: Place InternalStructure Hit Locations last!!
        this.internalStructureFactory.addInternalStructure(mech, internalStructureType);
        this.engineFactory.addEngine(mech, engineType, walkMovementPoints);
        this.gyroFactory.addGyro(mech, MechGyroType.NORMAL);

        // if (game != null)
        // game.getMechs().add(mech);
    }

    private final IMechHitLocationLeg addLeg(IMutableMech mech,
                                             MechHitLocationPosition position)
            throws BattletechException
    {
        IMechHitLocationLeg hitLocationLeg = addLeg(mech, position, calcInternalStructurePoints(mech, position));
        addActuator(hitLocationLeg, MechActuatorType.LEG_HIP, 0);
        addActuator(hitLocationLeg, MechActuatorType.LEG_UPPER, 1);
        addActuator(hitLocationLeg, MechActuatorType.LEG_LOWER, 2);
        addActuator(hitLocationLeg, MechActuatorType.LEG_FOOT, 3);
        return hitLocationLeg;
    }

    private IMechHitLocationArm addArm(BIPED_MECH mech,
                                       MechHitLocationPosition position,
                                       WhichArmActuators armActuators)
            throws BattletechException
    {
        IMechHitLocationArm hitLocationArm = addArm(mech, position, calcInternalStructurePoints(mech, position));
        addActuator(hitLocationArm, MechActuatorType.ARM_SHOULDER, 0);
        addActuator(hitLocationArm, MechActuatorType.ARM_UPPER, 1);

        switch (armActuators)
        {
            case NEITHER:
                break;

            case LOWER_ARM:
                addActuator(hitLocationArm, MechActuatorType.ARM_LOWER, 2);
                break;

            case LOWER_ARM_AND_HAND:
                addActuator(hitLocationArm, MechActuatorType.ARM_LOWER, 2);
                addActuator(hitLocationArm, MechActuatorType.ARM_HAND, 3);
                break;
        }

        return hitLocationArm;
    }

    private IMechHitLocationHead addHead(IMutableMech mech)
            throws BattletechException
    {
        IMechHitLocationHead head = addHead(mech, calcInternalStructurePoints(mech, MechHitLocationPosition.HEAD));
        addCockpit(head, 2);
        addSensors(head, 1, 4);
        addLifeSupport(head, 0, 5);
        return head;
    }

    protected IMechHitLocationCenterTorso addCenterTorso(IMutableMech mech)
            throws BattletechException
    {
        return addCenterTorso(mech, calcInternalStructurePoints(mech, MechHitLocationPosition.CENTER_TORSO));
    }

    protected IMechHitLocationSideTorso addSideTorso(IMutableMech mech,
                                                     MechHitLocationPosition position)
            throws BattletechException
    {
        return addSideTorso(mech, position, calcInternalStructurePoints(mech, position));
    }

    private int calcInternalStructurePoints(IMutableMech mech,
                                            MechHitLocationPosition position)
            throws BattletechException
    {
        return this.internalStructureFactory.getInternalStructurePointsForHitLocationType(mech,
                                                                                          position.getMechHitLocationType());

    }
}
